package com.wlyuan.index.domain.index.builder;

import com.wlyuan.core.domain.event.DomainEventPublisher;
import com.wlyuan.index.api.dto.QuotationIndexDTO;
import com.wlyuan.index.api.dto.combine.CombineIndexDTO;
import com.wlyuan.index.domain.index.CombineIndex;
import com.wlyuan.index.domain.index.event.CombineIndexDamaged;
import com.wlyuan.index.domain.index.valueobj.*;
import com.wlyuan.index.domain.source.Source;
import com.wlyuan.index.domain.source.adapter.CombineAdapter;
import com.wlyuan.index.domain.source.adapter.QuotationAdapter;
import com.wlyuan.index.domain.source.utils.SourceUtils;
import com.wlyuan.transport.dto.query.CarrierDTO;
import com.wlyuan.transport.dto.query.CombineCarrierDTO;
import com.wlyuan.transport.dto.query.CombineCustomerDTO;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yuanjie
 */
@Slf4j
@NoArgsConstructor
public class CombineIndexBuilder {
    @Mapper(imports = {Bill.class, BooleanUtils.class}, uses = {SourceUtils.class})
    public interface Assembler {
        @Mappings({
                @Mapping(target = "trackNo", source = "device.deviceCode"),
                @Mapping(target = "trackType", source = "device.trackType", defaultValue = "0"),
                @Mapping(target = "closeType", source = "close.type"),
                @Mapping(target = "closeStatus", source = "close.status"),
                @Mapping(target = "totalFee", source = "price.totalFee"),
                @Mapping(target = "hasQuote", source = "price.quotationStatus"),
                @Mapping(target = "endBill", expression = "java(Bill.ENDED.equals(source.getBillDayStatus()))"),
        })
        void assemble(@MappingTarget CombineIndex combine, CombineAdapter source);

        @Mappings({
                @Mapping(target = "tenantId", ignore = true)
        })
        void assemble(@MappingTarget CombineIndex index, CombineCarrierDTO source);

        @Mappings({
                @Mapping(target = "tenantId", ignore = true),
                @Mapping(target = "customerName", source = "showCustomerName"),
        })
        void assemble(@MappingTarget CombineIndex index, CombineCustomerDTO source);

        CombineIndex.Waybill assembleWaybill(CombineIndexDTO.Waybill source);

        @Mappings({
                @Mapping(target = "quotations", ignore = true),
//                @Mapping(target = "bills", ignore = true),
//                @Mapping(target = "balances", ignore = true),
        })
        void assembleRelation(@MappingTarget CombineRelation relation, CombineRelation source);

        @Mapping(target = "source", source = "sourceType")
        Quotation assembleQuotation(QuotationAdapter source);
    }

    private static final Assembler ASSEMBLER = Mappers.getMapper(Assembler.class);
    private CombineIndex index = new CombineIndex();
    private CombineAdapter combine;
    private final List<QuotationAdapter> quotations = new ArrayList<>();

    public CombineIndexBuilder(Source source) {
        this.combine = new CombineAdapter(source.decode(CombineIndexDTO.class));
    }

    public CombineIndexBuilder withIndex(CombineIndex index) {
        this.index = index;
        return this;
    }

    public CombineIndexBuilder withBounds(List<Source> sources) {
        if (CollectionUtils.isEmpty(sources)) {
            return this;
        }
        sources.forEach(this::withBound);
        return this;
    }

    public CombineIndexBuilder withBound(Source source) {
        if (null == source) {
            return this;
        }
        if (source.isQuotation()) {
            this.quotations.add(new QuotationAdapter(source.decode(QuotationIndexDTO.class)));
        }
        return this;
    }

    private void addRelation(CombineRelation relation) {
        // 将新的数据合并到已存在数据中
        this.index.addRelation(relation, ASSEMBLER::assembleRelation);
    }

    private boolean isOnlineCarrier(CarrierDTO carrier) {
        return null != carrier && Carrier.isOnline(carrier.getCarrierType());
    }

    private void buildRelations(CombineIndexDTO combine) {
        var tenantIds = new ArrayList<Long>();
        // 作为发货方的时候，查看我的承运方运单
        var upstream = new CombineRelation();
        upstream.setTenantId(combine.getTenantId());
        upstream.setCharacter(Characters.CUSTOMER);
        upstream.setStatus(CombineStatus.apply(Characters.CUSTOMER, combine.getStatus()));
        this.addRelation(upstream);
        tenantIds.add(combine.getTenantId());
        // 承运方信息不为空， 查看我的发货方运单
        if (isOnlineCarrier(combine.getCarrier())) {
            var carrier = combine.getCarrier();
            var downstream = new CombineRelation();
            downstream.setTenantId(carrier.getCarrierTenantId());
            downstream.setCharacter(Characters.CARRIER);
            downstream.setStatus(CombineStatus.apply(Characters.CARRIER, combine.getStatus()));
            this.addRelation(downstream);
            tenantIds.add(carrier.getCarrierTenantId());
        }
        this.index.checkRelations(tenantIds);
    }


    private void buildRelations(QuotationAdapter adapter) {
        if (CollectionUtils.isEmpty(adapter.getRelations())) {
            logger.error("Quotation relations empty: {}", adapter);
            return;
        }

        // 草稿状态只添加所属方
//        if (adapter.isDraft()) {
//            var quotation = ASSEMBLER.assembleQuotation(adapter);
//            this.index.addQuotation(quotation, adapter.getTenantId());
//            return;
//        }

        adapter.getRelations().forEach(relation -> {
            var quotation = ASSEMBLER.assembleQuotation(adapter);
            quotation.setStatus(relation.getStatus());
            this.index.addQuotation(quotation, relation.getTenantId());
        });
    }

    public CombineIndex build() {
        if (null != this.combine) {
            ASSEMBLER.assemble(this.index, this.combine);
            ASSEMBLER.assemble(this.index, this.combine.getCarrier());
            ASSEMBLER.assemble(this.index, this.combine.getCustomer());
            this.buildRelations(this.combine);
        }

        this.quotations.forEach(this::buildRelations);

        if (CollectionUtils.isEmpty(this.index.getRelations())) {
            logger.error("Combine relations no found: {}", this.index.getCombineId());
            DomainEventPublisher.publish(new CombineIndexDamaged(this.index.getCombineId()));
        }

        this.index.buildRelations();
        this.index.setIndexAt(LocalDateTime.now());
        return this.index;
    }
}
